Java Unit Test Guidelines
General Rules
Our testing strategy wants to ensure that any change in the code would be caught by a test. This way, we want to be able to rely on tests when we want to fix bugs/develop new features; so that we ensure the code would still work after the changes. It is essential for the tests to be easy to read and understand, so that the collaboration among the developers are easier.
-
Prioritize Code Smells and Bugs
Code smells and bugs should be addressed with the highest priority. -
Integration Tests
- Written in the Controller level without mocks.
- Focus on checking the integration of layers.
- Avoid extensive testing of implementation details; this should be covered in unit tests.
-
Layered Testing
- Separate unit tests for each layer of the application (Controller, Service, etc.).
- Unit tests should be independent and not rely on the state of the database or other external dependencies.
- Mocking should be used to isolate the unit being tested.
- Extensive testing of each implementation detail.
-
Verification of Calls
- All calls to other components/layers must be verified in unit tests.
- Assert the parameters passed to these calls.
-
Attribute/Response Assertions
- Every attribute and response should be asserted.
- Ensure that changes in the code do not alter expected values or formats (e.g., date formats).
-
Parameterized Tests
- Use parameterized tests to run the same logic with different inputs.
- Use constants for tested values.
- This practice reduces code duplication and improves test coverage.
-
Appropriate Assertions
- Use various assertions (e.g.,
assertTrue
,assertFalse
,assertThrows
) to check conditions beyond equality. - This approach provides clearer intent and better feedback when tests fail.
- Use various assertions (e.g.,
Arrange-Act-Assert Pattern
Structure your tests using the Arrange-Act-Assert pattern to enhance readability:
-
Arrange: Set up necessary preconditions and inputs. This includes initializing objects, setting up mock responses, and defining required data inputs for the test scenario.
-
Act: Execute the method under test. This involves invoking the functionality to be tested with the arranged inputs.
-
Assert: Verify the outcome. Check the results returned by the method against expected outcomes, asserting values, states, or behaviors.
Example for Unit Tests
Assuming we have a simple Spring Boot application with the following layers:
- Controller:
UserController
- Service:
UserService
- Repository:
UserRepository
UserController Example
UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
}
UserController.java
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Mock
private UserService userService;
@InjectMocks
private UserController userController;
private static final Long USER_ID = 1L;
private static final String USER_NAME = "John Doe";
private static final String USER_EMAIL = "john@example.com";
private static final String USER_URL = "/users/" + USER_ID;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testGetUserById() throws Exception {
// Arrange
User user = new User(USER_ID, USER_NAME, USER_EMAIL);
when(userService.findById(USER_ID)).thenReturn(user);
// Act
mockMvc.perform(get(USER_URL)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value(USER_NAME))
.andExpect(jsonPath("$.email").value(USER_EMAIL));
// Assert
verify(userService).findById(USER_ID); // Verify service call
}
}
UserService Example
UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
UserServiceTest.java
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
private static final Long USER_ID = 1L;
private static final String USER_NAME = "John Doe";
private static final String USER_EMAIL = "john@example.com";
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testFindById() {
// Arrange
User user = new User(USER_ID, USER_NAME, USER_EMAIL);
when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user));
// Act
User foundUser = userService.findById(USER_ID);
// Assert
assertNotNull(foundUser);
assertEquals(USER_NAME, foundUser.getName());
assertEquals(USER_EMAIL, foundUser.getEmail());
verify(userRepository).findById(USER_ID); // Verify repository call
}
@Test
public void testFindById_UserNotFound() {
// Arrange
when(userRepository.findById(USER_ID)).thenReturn(null);
// Act
User foundUser = userService.findById(USER_ID);
// Assert
assertNull(foundUser); // Assert that the user is null when not found
verify(userRepository).findById(USER_ID); // Verify repository call
}
}
UserRepository Example
UserRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
UserRepositoryTest.java
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
private static final String USER_NAME = "John Doe";
private static final String USER_EMAIL = "john@example.com";
@Test
public void testSaveUser() {
//Arrange
User user = new User(null, USER_NAME, USER_EMAIL);
//Act
User savedUser = userRepository.save(user);
//Assert
assertNotNull(savedUser.getId());
assertEquals(USER_NAME, savedUser.getName());
assertEquals(USER_EMAIL, savedUser.getEmail());
}
@Test
public void testFindUser() {
//Arrange
User user = new User(null, USER_NAME, USER_EMAIL);
User savedUser = userRepository.save(user);
//Act
User foundUser = userRepository.findById(savedUser.getId()).orElse(null);
//Assert
assertNotNull(foundUser);
assertEquals(savedUser.getId(), foundUser.getId());
assertEquals(USER_NAME, foundUser.getName());
assertEquals(USER_EMAIL, foundUser.getEmail());
}
}